Skip to content

Add plugin NewsNow v1.0.0#298

Closed
crisybox wants to merge 2 commits into
ZToolsCenter:mainfrom
crisybox:plugin/newsnow
Closed

Add plugin NewsNow v1.0.0#298
crisybox wants to merge 2 commits into
ZToolsCenter:mainfrom
crisybox:plugin/newsnow

Conversation

@crisybox

@crisybox crisybox commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

插件信息

  • 名称: NewsNow
  • 插件ID: newsnow
  • 版本: 1.0.0
  • 描述: 实时新闻快讯与多平台热搜订阅
  • 作者: crisy
  • 类型: 新增

本次变更

  • 开发: news-now新闻资讯订阅

截图 / 演示

自检清单

  • plugin.json 的 name / title / version / description / author 字段均已检查
  • 已移除调试日志、未使用文件、敏感信息(.env、token、密钥等)
  • 本次 PR 的 diff 仅涉及 plugins/newsnow/ 目录
  • 已在本地 ZTools 客户端实际加载并测试过此插件,主要功能正常
  • 同意以仓库声明的开源协议发布此插件

此 PR 由 ztools-plugin-cli 自动管理:每次 ztools publish 在分支上追加一个 commit,PR 链接保持不变。

@crisybox

crisybox commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author
1783498913091

@crisybox crisybox marked this pull request as ready for review July 8, 2026 08:22

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the 'NewsNow' plugin, which provides real-time news and multi-platform hot search subscriptions. The implementation includes frontend assets, configuration files, and a Node.js-based preload script for handling API requests. However, several critical security and stability issues must be addressed. Specifically, there are two XSS vulnerabilities in the frontend JavaScript where remote API data and URL variables are rendered directly into the DOM without HTML escaping. Additionally, the preload script's redirect handling lacks a maximum limit, which could result in infinite redirect loops and denial of service.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread plugins/newsnow/assets/main-CVdvgvJU.js Outdated
Comment on lines +91 to +104
</div>`}function ee(e,t={}){var p,b;const{preview:n=!1}=t,o=x[e];if(!o)return"";const a=g.get(e)??{},r=i.focusSources.includes(e),s=O===e,c=a.error?a.error:a.loading?"加载中...":(p=a.data)!=null&&p.updatedTime?`${ie(a.data.updatedTime)}更新`:"",u=((b=a.data)==null?void 0:b.items)??[],d=u.length>0?o.type==="hottest"?`<ol class="nn-list-hot">${u.map((l,f)=>{const m=$({sourceId:e,id:l.id}),y=X(i.favorites,m),S=l.url||"#";return`<li>
<a href="#" class="nn-fav-link" data-url="${encodeURIComponent(S)}"
data-fav-source="${e}" data-fav-id="${l.id}"
data-fav-title="${encodeURIComponent(l.title)}" data-fav-url="${encodeURIComponent(S)}">
<span class="nn-rank">${f+1}</span>
<span>${l.title}</span>
<span class="nn-item-star ${y?"active":""}" data-fav-source="${e}" data-fav-id="${l.id}" data-fav-title="${encodeURIComponent(l.title)}" data-fav-url="${encodeURIComponent(S)}">${y?"★":"☆"}</span>
</a>
</li>`}).join("")}</ol>`:`<ol class="nn-list-timeline">${u.map(l=>{var S,E;const f=l.url||"#",m=l.id||`${l.title}-${l.pubDate||""}`;return`<li>
<div class="meta"><span>${ie(l.pubDate||((S=l.extra)==null?void 0:S.date))}</span><span>${((E=l.extra)==null?void 0:E.info)||""}</span></div>
<a href="#" class="nn-fav-link" data-url="${encodeURIComponent(f)}"
data-fav-source="${e}" data-fav-id="${m}"
data-fav-title="${encodeURIComponent(l.title)}" data-fav-url="${encodeURIComponent(f)}">${l.title}</a>
</li>`}).join("")}</ol>`:a.error?`<div class="nn-card-error">${a.error}</div>`:'<div class="nn-empty" style="padding:16px">暂无数据</div>',h=o.color||"slate";return`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

在渲染卡片内容时,直接将远程 API 返回的 l.title 插入到 HTML 模板中(例如 <span>${l.title}</span>),并且没有进行任何 HTML 转义。如果第三方新闻源或热搜接口返回了包含恶意 HTML/JavaScript 脚本的标题(例如 <img src=x onerror=alert(1)>),在通过 innerHTML 渲染时会直接触发跨站脚本攻击(XSS)。

由于该插件运行在 ZTools 客户端环境,XSS 漏洞可能会被利用来调用敏感的客户端 API,造成严重的安全隐患。

建议修复方案:
在将数据插入 HTML 之前,必须对所有来自远程 API 的动态文本(如 l.titlel.extra 等)进行 HTML 实体转义。可以实现一个简单的转义函数,例如:

function escapeHtml(str) {
  if (!str) return '';
  return str.replace(/[&<>'"/]/g, tag => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    "'": '&#39;',
    '"': '&quot;',
    '/': '&#x2F;'
  }[tag] || tag));
}

然后在模板字符串中使用 escapeHtml(l.title) 代替直接的 ${l.title}

Comment on lines +29 to +85
function requestJson(urlString) {
return new Promise((resolve, reject) => {
let url;
try {
url = new URL(urlString);
} catch (error) {
reject(error);
return;
}

const origin = `${url.protocol}//${url.host}`;
const lib = url.protocol === "https:" ? https : http;
const options = {
hostname: url.hostname,
port: url.port || (url.protocol === "https:" ? 443 : 80),
path: `${url.pathname}${url.search}`,
method: "GET",
headers: buildHeaders(origin),
};

const req = lib.request(options, (res) => {
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
requestJson(new URL(res.headers.location, urlString).toString()).then(resolve).catch(reject);
return;
}
let body = "";
res.setEncoding("utf8");
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
if (res.statusCode && res.statusCode >= 400) {
reject(new Error(parseErrorMessage(body, res.statusCode)));
return;
}
try {
const json = JSON.parse(body);
if (json.error) {
reject(new Error(json.message || json.statusMessage || "请求失败"));
return;
}
resolve(json);
} catch (error) {
reject(new Error("响应不是有效 JSON"));
}
});
});

req.on("error", reject);
req.on("timeout", () => {
req.destroy();
reject(new Error("请求超时"));
});
req.setTimeout(20000);
req.end();
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

在处理 HTTP 重定向时,缺少对重定向次数的限制,这可能会导致无限重定向循环(例如 A -> B -> A),从而引发栈溢出或请求挂起(拒绝服务)。此外,也应当限制重定向的目标协议仅为 http:https:,以防止潜在的协议走私或非预期行为。

建议引入一个重定向计数器(例如最大允许 5 次重定向),并对 URL 协议进行校验。

function requestJson(urlString, redirectCount = 0) {
  return new Promise((resolve, reject) => {
    if (redirectCount > 5) {
      reject(new Error("重定向次数过多"));
      return;
    }
    let url;
    try {
      url = new URL(urlString);
    } catch (error) {
      reject(error);
      return;
    }

    if (url.protocol !== "http:" && url.protocol !== "https:") {
      reject(new Error("不支持的协议类型"));
      return;
    }

    const origin = `${url.protocol}//${url.host}`;
    const lib = url.protocol === "https:" ? https : http;
    const options = {
      hostname: url.hostname,
      port: url.port || (url.protocol === "https:" ? 443 : 80),
      path: `${url.pathname}${url.search}`,
      method: "GET",
      headers: buildHeaders(origin),
    };

    const req = lib.request(options, (res) => {
      if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
        const redirectUrl = new URL(res.headers.location, urlString).toString();
        requestJson(redirectUrl, redirectCount + 1).then(resolve).catch(reject);
        return;
      }
      let body = "";
      res.setEncoding("utf8");
      res.on("data", (chunk) => {
        body += chunk;
      });
      res.on("end", () => {
        if (res.statusCode && res.statusCode >= 400) {
          reject(new Error(parseErrorMessage(body, res.statusCode)));
          return;
        }
        try {
          const json = JSON.parse(body);
          if (json.error) {
            reject(new Error(json.message || json.statusMessage || "请求失败"));
            return;
          }
          resolve(json);
        } catch (error) {
          reject(new Error("响应不是有效 JSON"));
        }
      });
    });

    req.on("error", reject);
    req.on("timeout", () => {
      req.destroy();
      reject(new Error("请求超时"));
    });
    req.setTimeout(20000);
    req.end();
  });
}

Comment thread plugins/newsnow/assets/main-CVdvgvJU.js Outdated
Comment on lines +2 to +9
<div class="nn-webview">
<div class="nn-webview-toolbar">
<button type="button" data-action="webview-close">← 返回</button>
<span class="nn-webview-title" title="${e}">${e}</span>
<button type="button" data-action="webview-external">外部</button>
</div>
<iframe class="nn-webview-frame" src="${e}" referrerpolicy="no-referrer"></iframe>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

en(e) 函数中,直接将 URL 变量 e 插入到 iframesrc 属性和 spantitle 属性中,并通过 innerHTML 动态渲染。如果 URL 包含双引号或恶意字符(例如 https://example.com/" onload="alert(1)),将会导致 HTML 属性注入和跨站脚本攻击(XSS)。

建议修复方案:
避免直接使用模板字符串拼接 HTML 并通过 innerHTML 渲染。建议使用 document.createElement 动态创建 DOM 元素,并通过安全的方式(如 element.src = eelement.textContent = e)设置属性和文本内容,或者在拼接前对 e 进行严格的 HTML 属性转义。

@crisybox

crisybox commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author
image

@lzx8589561

Copy link
Copy Markdown
Contributor

感谢pr,目前插件仓库仅支持源码提交,编译后的不支持

@lzx8589561 lzx8589561 closed this Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants